﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SQLite;
using System.Net;

namespace Chatter.CoreServer
{
    /// <summary>
    /// Classe permettant la gestion de la base de données
    /// </summary>
    public sealed class Db
    {
        private static Db _db;
        private SQLiteConnection _connexion;
        private bool _opened;

        /// <summary>
        /// Ouverture de la connexion. Evite une double ouverture et un crash.
        /// </summary>
        private void _OpenConnexion()
        {
            if (!this._opened)
            {
                this._connexion.Open();
                this._opened = true;
            }
        }

        /// <summary>
        /// Fermeture de la connexion.
        /// </summary>
        private void _DisconnectConnexion()
        {
            if (this._opened)
            {
                this._connexion.Close();
                this._opened = false;
            }
        }
        
        /// <summary>
        /// Connexion à la base de données SQLite
        /// </summary>
        public SQLiteConnection connexion
        {
            get
            {
                return _connexion;
            }
            set
            {
                _connexion = value;
            }
        }

        /// <summary>
        /// Singleton qui assure l'instanciation unique de cette classe
        /// </summary>
        public static Db GetDB()
        {
            if (_db == null)
            {
                _db = new Db();
            }
            return _db;
        }


        public void CreateBase()
        {
            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine("-----------------------------------------------------------------");
            Console.WriteLine();
            Console.WriteLine("La base de données n'existe pas. Elle a été créée.");
            Console.WriteLine();


            this._OpenConnexion();

            SQLiteCommand cmd = this._connexion.CreateCommand();

            cmd.CommandText = "CREATE TABLE Bans ('ip' VARCHAR(15) not null, 'admin' VARCHAR(32) not null, 'permanent' BOOLEAN(32) not null, 'date' VARCHAR(19) not null, 'duree' INT(32) not null, 'raison' VARCHAR(255) not null);";
            cmd.ExecuteReader();
            cmd.Dispose();

            cmd.CommandText = "CREATE TABLE Rooms ('name' VARCHAR(32) not null, 'pwdProtected' BOOLEAN not null, 'password' VARCHAR(32) not null, 'locked' BOOLEAN not null );";
            cmd.ExecuteReader();
            cmd.Dispose();

            cmd.CommandText = "CREATE TABLE Users ('username' VARCHAR(32) not null, 'password' VARCHAR(32) not null, 'type' VARCHAR(32) not null, 'email' VARCHAR(128) not null );";
            cmd.ExecuteReader();
            cmd.Dispose();

            cmd.CommandText = "INSERT INTO Users('username', 'password', 'type', 'email') VALUES ('admin', '" + Config.GetConfig().CalculateMD5Hash("ctr2013") + "', 'Administrator', '');";
            cmd.ExecuteReader();
            cmd.Dispose();

            this._DisconnectConnexion();

            Console.WriteLine();
            Console.WriteLine("Compte Administrateur par défaut : ");
            Console.WriteLine("Nom d'utilisateur : admin");
            Console.WriteLine("Mot de passe : ctr2013");
            Console.WriteLine();
            Console.WriteLine(" !! Changez le mot de passe à votre premier connexion !! ");
            Console.WriteLine();
            Console.WriteLine("-----------------------------------------------------------------");
            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine();
        }

        /// <summary>
        /// Récupération des salons stockés en base de données
        /// </summary>
        /// <returns>Liste de salons</returns>
        public List<ChatRoom> GetRooms()
        {
            this._OpenConnexion();
            List<ChatRoom> rooms = new List<ChatRoom>();

            // Room par défaut
            rooms.Add(new ChatRoom("Default", RoomType.Permanent, false));

            SQLiteCommand cmd = this._connexion.CreateCommand();
            cmd.CommandText = "SELECT * FROM Rooms";

            SQLiteDataReader rd = cmd.ExecuteReader();

            while (rd.Read())
            {
                string name = rd["name"].ToString();
                RoomType type = RoomType.Permanent;
                bool pwdProtected = Convert.ToBoolean(rd["pwdProtected"]);
                string password = rd["password"].ToString();
                bool locked = Convert.ToBoolean(rd["locked"]);

                rooms.Add(new ChatRoom(name, type, pwdProtected, password, locked));
            }

            rd.Dispose();
            cmd.Dispose();
            this._DisconnectConnexion();

            return rooms;
        }

        /// <summary>
        /// Ajoute un salon dans la base de données
        /// </summary>
        /// <param name="room">Objet salon à ajouter</param>
        /// <returns></returns>
        public void AddRoom(ChatRoom room)
        {    
            try
            {
                this._OpenConnexion();

                SQLiteCommand cmd = this._connexion.CreateCommand();
                cmd.CommandText = "INSERT INTO Rooms (name, pwdProtected, password, locked) VALUES ('" + room.name + "', '" + room.pwdProtected + "', '" + room.password + "', '" + room.locked + "')";
                cmd.ExecuteReader();
                cmd.Dispose();

                this._DisconnectConnexion();
            }
            catch(SQLiteException e)
            {
                throw new ChatterException("Erreur lors de l'ajout d'un salon !" + "\n\n" + e + '\n');
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="roomName"></param>
        /// <returns></returns>
        public bool CheckRoomExists(string roomName)
        {
            this.connexion.Open();

            SQLiteCommand cmd = this.connexion.CreateCommand();
            cmd.CommandText = "SELECT name FROM Rooms WHERE name='" + roomName + "'";
            SQLiteDataReader rd = cmd.ExecuteReader(System.Data.CommandBehavior.SingleResult);

            bool isExists = rd.Read();

            cmd.Dispose();
            rd.Dispose();
            this.connexion.Close();

            return isExists;
        }

        /// <summary>
        /// Modification des préférences de l'utilisateur
        /// </summary>
        /// <param name="username">Nom d'utilisateur</param>
        /// <param name="password">Nouveau mot de passe</param>
        /// <param name="email">Nouvelle adresse email</param>
        public void UserSetPasswordAndMail(string username, string password, string email)
        {
            this._OpenConnexion();

            string separator = "";

            if(!String.IsNullOrEmpty(password) && !String.IsNullOrEmpty(email))
            {
                separator = ", ";
            }

            SQLiteCommand cmd = this._connexion.CreateCommand();
            cmd.CommandText = "UPDATE Users SET ";
            
            if(!String.IsNullOrEmpty(password))
            {
                cmd.CommandText += "password='" + password + "'" + separator;
            }

            if(!String.IsNullOrEmpty(email))
            {
                cmd.CommandText += "email='" + email + "'";
            }

            cmd.CommandText += " WHERE username LIKE '" + username + "'";
            
            cmd.ExecuteReader();
            cmd.Dispose();

            this._DisconnectConnexion();
        }

        /// <summary>
        /// Modification d'un salon permanent en base de données
        /// </summary>
        /// <param name="room">Paramètres du salon</param>
        /// <param name="oldName">Nom de l'ancien salon</param>
        public void UpdateRoom(ChatRoom room, string oldName)
        {
            try
            {
                this._OpenConnexion();

                SQLiteCommand cmd = this._connexion.CreateCommand();
                cmd.CommandText = "UPDATE Rooms SET name='" + room.name + "', pwdProtected='" + room.pwdProtected + "', password='" + room.password + "', locked='" + room.locked + "' WHERE name='" + oldName + "'";
                cmd.ExecuteReader();
                cmd.Dispose();
                this._DisconnectConnexion();
            }
            catch (SQLiteException e)
            {
                throw new ChatterException("Erreur lors de la modification d'un salon !" + "\n\n" + e + '\n');
            }
        }

        /// <summary>
        /// Supprimer un salon de la base de données
        /// </summary>
        /// <param name="room">Objet salon à supprimer</param>
        /// <returns></returns>
        public void RemoveRoom(ChatRoom room)
        {
            try
            {
                this._OpenConnexion();

                SQLiteCommand cmd = this._connexion.CreateCommand();
                cmd.CommandText = "DELETE FROM Rooms WHERE name = '" + room.name + "'";
                cmd.ExecuteNonQuery();
                cmd.Dispose();

                this._DisconnectConnexion();
            }
            catch (SQLiteException e)
            {
                throw new ChatterException("Erreur lors de la suppression d'un salon !" + "\n\n" + e + '\n');
            }
        }

        /// <summary>
        /// Ajoute un nouvel utilisateur dans la base de données
        /// </summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <param name="email"></param>
        /// <param name="type"></param>
        public void AddUser(string username, string password, string email, UserType type = UserType.Registered)
        {
            try
            {
                this.connexion.Open();

                SQLiteCommand cmd = this.connexion.CreateCommand();
                cmd.CommandText = "INSERT INTO Users (username, password, type, email) VALUES ('" + username + "', '" + password + "', '" + UserType.Registered + "', '"  + email + "')";
                cmd.ExecuteReader();
                cmd.Dispose();

                this.connexion.Close();
            }
            catch(SQLiteException e)
            {
                throw new ChatterException("Erreur lors de l'ajout d'un utilisateur !" + "\n\n" + e + '\n');
            }
        }

        /// <summary>
        /// Vérification si l'adresse IP de l'utilisateur est bannie
        /// </summary>
        /// <param name="ip">Adresse IP de l'utilisateur</param>
        /// <returns>True si bannie, false sinon</returns>
        public bool CheckUserIsBan(IPAddress ip)
        {
            this.connexion.Open();

            SQLiteCommand cmd = this.connexion.CreateCommand();
            cmd.CommandText = "SELECT * FROM Bans WHERE ip='" + ip.ToString() + "'";
            SQLiteDataReader rd = cmd.ExecuteReader(System.Data.CommandBehavior.SingleResult);

            bool isBan = rd.Read();

            cmd.Dispose();
            rd.Dispose();
            this.connexion.Close();

            return isBan;
        }

        /// <summary>
        /// Vérification des durées de vie des bannissements
        /// </summary>
        public void CheckBansLifeTime()
        {
            this.connexion.Open();

            SQLiteCommand cmd = this.connexion.CreateCommand();
            cmd.CommandText = "SELECT * FROM Bans WHERE permanent=0";
            SQLiteDataReader rd = cmd.ExecuteReader();
            
            List<string> usersIpToDeban = new List<string>();

            while (rd.Read())
            {
                DateTime dateBan = Convert.ToDateTime(rd["date"]);
                int dureeBan = Convert.ToInt32(rd["duree"]);
                DateTime dateDeban = dateBan.AddMinutes(dureeBan);
                DateTime dateNow = DateTime.Now;

                // Compare retourne -1 si antérieur, 0 si égal, 1 si ultérieur
                if (dateDeban.CompareTo(dateNow).Equals(-1))
                {
                    // La date de déban dépasse la date du jour, il faut alors supprimer le bannissement.
                    usersIpToDeban.Add(rd["ip"].ToString());
                }
            }

            cmd.Dispose();
            rd.Dispose();
            this.connexion.Close();

            // Si il y a des utilisateurs à débannir
            if (usersIpToDeban.Count() > 0)
            {
                this._DebanUsersIp(usersIpToDeban);
            }
        }

        /// <summary>
        /// Débannir une liste d'utilisateurs
        /// </summary>
        /// <param name="usersIP">Liste des adresses IP des utilisateurs à débannir</param>
        private void _DebanUsersIp(List<string> usersIP)
        {
            this.connexion.Open();
            SQLiteCommand cmd = this.connexion.CreateCommand();

            foreach (string userIP in usersIP)
            {
                cmd.CommandText = "DELETE FROM Bans WHERE ip ='" + userIP + "'";
                cmd.ExecuteNonQuery();
                Console.WriteLine("DEBAN : " + userIP);
            }

            cmd.Dispose();
            this.connexion.Close();
        }

        /// <summary>
        /// Vérification des informations de connexion de l'utilisateur
        /// </summary>
        /// <param name="username">Nom d'utilisateur</param>
        /// <param name="password">Mot de passe</param>
        /// <returns></returns>
        public bool CheckUserCreditentials(string username, string password)
        {
            this.connexion.Open();

            SQLiteCommand cmd = this.connexion.CreateCommand();
            cmd.CommandText = "SELECT * FROM Users WHERE username LIKE '" + username + "' AND password='" + password + "'";
            SQLiteDataReader rd = cmd.ExecuteReader(System.Data.CommandBehavior.SingleResult);
            
            bool isCorrect = rd.Read();

            cmd.Dispose();
            rd.Dispose();
            this.connexion.Close();

            return isCorrect;
        }

        /// <summary>
        /// Vérification si l'adresse email existe dans la table des utilisateurs
        /// </summary>
        /// <param name="email">Adresse email à vérifier</param>
        /// <returns></returns>
        public bool CheckUserEmailExists(string email)
        {
            this.connexion.Open();

            SQLiteCommand cmd = this.connexion.CreateCommand();
            cmd.CommandText = "SELECT username FROM Users WHERE email='" + email + "'";
            SQLiteDataReader rd = cmd.ExecuteReader(System.Data.CommandBehavior.SingleResult);

            bool isExists = rd.Read();
            
            cmd.Dispose();
            rd.Dispose();
            this.connexion.Close();

            return isExists;
        }

        /// <summary>
        /// Méthode qui permet de retrouver le nom de l'utilisateur à partir de son adresse email
        /// </summary>
        /// <param name="email">Adresse email de l'utilisateur</param>
        /// <returns>Nom de l'utilisateur associé</returns>
        public string UserGetNameByEmail(string email)
        {
            string username = "";
            this.connexion.Open();

            SQLiteCommand cmd = this.connexion.CreateCommand();
            cmd.CommandText = "SELECT username FROM Users WHERE email='" + email + "'";
            SQLiteDataReader rd = cmd.ExecuteReader(System.Data.CommandBehavior.SingleResult);

            if(rd.Read())
            {
                username = rd["username"].ToString();
            }
            cmd.Dispose();
            rd.Dispose();
            this.connexion.Close();
            
            return username;
        }

        /// <summary>
        /// Récupère le type d'un utilisateur par rapport à son nom d'utilisateur
        /// </summary>
        /// <param name="name">Nom d'utilisateur</param>
        /// <returns>Type de l'utilisateur (Anonyme, Enregistré ou Administrateur)</returns>
        public UserType UserGetTypeByName(string name)
        {
            UserType type;
            this.connexion.Open();

            SQLiteCommand cmd = this.connexion.CreateCommand();
            cmd.CommandText = "SELECT type FROM Users WHERE username LIKE '" + name + "'";
            SQLiteDataReader rd = cmd.ExecuteReader(System.Data.CommandBehavior.SingleResult);

            if (rd.Read())
            {
                type = (UserType)Enum.Parse(typeof(UserType), rd["type"].ToString());
            }
            else
            {
                type = UserType.Anonymous;
            }
            cmd.Dispose();
            rd.Dispose();
            this.connexion.Close();

            return type;
        }

        /// <summary>
        /// Vérification si l'utilisateur est Administrateur
        /// </summary>
        /// <param name="email">Email de l'utilisateur</param>
        /// <returns>True si oui, False sinon</returns>
        public bool UserIsAdministrator(string email)
        {
            UserType type;
            this.connexion.Open();

            SQLiteCommand cmd = this.connexion.CreateCommand();
            cmd.CommandText = "SELECT type FROM Users WHERE email='" + email + "'";
            SQLiteDataReader rd = cmd.ExecuteReader(System.Data.CommandBehavior.SingleResult);

            if (rd.Read())
            {
                type = (UserType)Enum.Parse(typeof(UserType), rd["type"].ToString());
            }
            else
            {
                type = UserType.Anonymous;
            }
            cmd.Dispose();
            rd.Dispose();
            this.connexion.Close();

            if (type.Equals(UserType.Administrator))
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Méthode qui permet de modifier le mot de passe de l'utilisateur associé à une adresse email
        /// </summary>
        /// <param name="email">Adresse email de l'utilisateur</param>
        /// <param name="newPassword">Nouveau mot de passe à attribuer</param>
        public void UserSetPasswordByEmail(string email, string newPassword)
        {
            this.connexion.Open();

            SQLiteCommand cmd = this.connexion.CreateCommand();
            cmd.CommandText = "UPDATE Users SET password='" + newPassword + "' WHERE email='" + email + "'";
            cmd.ExecuteNonQuery();
            cmd.Dispose();
            this.connexion.Close();
        }
    }
}
